﻿//-----------------------------------------------------------------------
// <copyright file="CleanActEngine.cs" company="FastNET Services">
//     Copyright (c) 2007-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.ServiceProcess;
using System.Threading;
using FastNET.CleanAct.Client.ClientEngine.Managers.Communication;
//using FastNET.CleanAct.Client.ClientEngine.Managers.Console;
using FastNET.CleanAct.Client.ClientEngine.Managers.Processing;
using FastNET.CleanAct.Client.ClientEngine.Managers.Schedule;
using FastNET.CleanAct.Client.ClientEngine.Managers.Trigger;
using FastNET.CleanAct.Client.Interfaces.EventArguments;
using FastNET.CleanAct.Client.Interfaces.Managers;
using FastNET.CleanAct.Client.Interfaces.Managers.Communication;
using FastNET.CleanAct.Client.Interfaces.Structures;
using FastNET.CleanAct.Client.Win32;

#endregion

namespace FastNET.CleanAct.Client.ClientEngine
{
    /// <summary>
    /// Windows Service class for the CleanAct Engine
    /// </summary>
    public partial class CleanActEngine : ServiceBase
    {
        #region Variables

        private readonly ManualResetEvent _threadSuspended;
        private readonly IClientCommunicationManager _communicationManager;
        private readonly IProcessingManager _processingManager;
        //private readonly IConsoleManager _consoleManager;
        private readonly IScheduleManager _scheduleManager;
        private readonly ITriggerManager _triggerManager;

        private readonly Queue<ProcessingSet> _queuedProcessingSets;
        private readonly object _processLock;

        private Thread _managerThread;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the CleanActEngine class
        /// </summary>
        public CleanActEngine()
        {
            InitializeComponent();

            _threadSuspended = new ManualResetEvent(false);

            _queuedProcessingSets = new Queue<ProcessingSet>();

            _processLock = new object();

            //Initialize Managers
            _communicationManager = new ClientCommunicationManager();
            //_consoleManager = new ConsoleManager();
            _processingManager = new ProcessingManager();
            _scheduleManager = new ScheduleManager(); // added management
            _triggerManager = new TriggerManager();

            _scheduleManager.ScheduleFired += ScheduleManager_ScheduleFired;
        }

        #endregion

        #region Private Properties

        /// <summary>
        /// Gets a value indicating whether or not the thread status shows it is running
        /// </summary>
        private bool IsAlive
        {
            get
            {
                return _managerThread != null && _managerThread.IsAlive;
            }
        }

        #endregion

        #region Protected Service Overrides

        /// <summary>
        /// When implemented in a derived class, executes when a Start command is sent to the service by the Service Control Manager (SCM)
        /// or when the operating system starts (for a service that starts automatically). Specifies actions to take when the service starts.
        /// </summary>
        /// <param name="args"> Data passed by the start command. </param>
        protected override void OnStart(string[] args)
        {
            StartThread();
            //_communicationManager.OpenFactories();
            _scheduleManager.Start();
            _triggerManager.Enabled = true;

            base.OnStart(args);
        }

        ///<summary>
        /// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM).
        /// Specifies actions to take when a service stops running.
        ///</summary>
        protected override void OnStop()
        {
            StopThread();
            //_communicationManager.CloseFactories();
            _scheduleManager.Stop();
            _triggerManager.Enabled = false;

            base.OnStop();
        }

        /// <summary>
        /// When implemented in a derived class, <see cref="M:System.ServiceProcess.ServiceBase.OnContinue" /> runs when a Continue command
        /// is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service resumes normal functioning
        /// after being paused.
        /// </summary>
        protected override void OnContinue()
        {
            ResumeThread();
            //_communicationManager.OpenChannels();
            _scheduleManager.Resume();
            _triggerManager.Enabled = true;

            base.OnContinue();
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Pause command is sent to the service by the Service Control Manager (SCM).
        /// Specifies actions to take when a service pauses.
        /// </summary>
        protected override void OnPause()
        {
            PauseThread();
            //_communicationManager.CloseChannels();
            _scheduleManager.Pause();
            _triggerManager.Enabled = false;

            base.OnPause();
        }

        /// <summary>
        /// When implemented in a derived class, executes when the computer's power status has changed. This applies to laptop computers
        /// when they go into suspended mode, which is not the same as a system shutdown.
        /// </summary>
        /// <param name="powerStatus">
        /// A <see cref="T:System.ServiceProcess.PowerBroadcastStatus" /> that indicates a notification from the system about its power status.
        /// </param>
        /// <returns> 
        /// When implemented in a derived class, the needs of your application determine what value to return. For example, if a
        /// QuerySuspend broadcast status is passed, you could cause your application to reject the query by returning false.
        /// </returns>
        protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
        {
            // TODO: Being added in Version 1.5-2.0
            switch (powerStatus)
            {
                case PowerBroadcastStatus.BatteryLow:
                case PowerBroadcastStatus.OemEvent:
                case PowerBroadcastStatus.PowerStatusChange:
                case PowerBroadcastStatus.QuerySuspend:
                case PowerBroadcastStatus.QuerySuspendFailed:
                case PowerBroadcastStatus.ResumeCritical:
                case PowerBroadcastStatus.ResumeAutomatic:
                case PowerBroadcastStatus.ResumeSuspend:
                case PowerBroadcastStatus.Suspend:
                    break;
            }

            return base.OnPowerEvent(powerStatus);
        }

        /// <summary>
        /// Executes when a change event is received from a Terminal Server session. 
        /// </summary>
        /// <param name="changeDescription"> A <see cref="T:System.ServiceProcess.SessionChangeDescription" /> structure that identifies the change type.</param>
        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            // Use Terminal Services to get the token
            WindowsIdentity windowsID = WindowsTerminalServer.GetSessionToken(changeDescription.SessionId);

            // Handle the event
            switch (changeDescription.Reason)
            {
                case SessionChangeReason.SessionLock:
                    _triggerManager.SessionLocked(windowsID);
                    break;

                case SessionChangeReason.SessionUnlock:
                    _triggerManager.SessionUnlocked(windowsID);
                    break;

                case SessionChangeReason.SessionLogoff:
                    _triggerManager.SessionLogoff(windowsID);
                    break;

                case SessionChangeReason.SessionLogon:
                    _triggerManager.SessionLogon(windowsID);
                    break;

                default:
                    EventLog.WriteEntry(String.Format("DevLog {0}:{1}", windowsID.Name, changeDescription.Reason));
                    break;
            }

            base.OnSessionChange(changeDescription);
        }

        /////// <summary>
        /////// When implemented in a derived class, executes when the system is shutting down. Specifies what should occur immediately prior to the system shutting down.
        /////// </summary>
        ////protected override void OnShutdown()
        ////{
        ////    if (true)
        ////    {
        ////    }

        ////    base.OnShutdown();
        ////}

        #endregion

        #region Private Main Thread Management

        /// <summary>
        /// Starts the manager background thread
        /// </summary>
        private void StartThread()
        {
            if (IsAlive)
            {
                throw new InvalidOperationException("Stop must be called before restarting");
            }

            _managerThread = new Thread(ManagerThread)
                             {
                                 Name = "CleanAct Core Thread",
                                 Priority = ThreadPriority.BelowNormal
                             };

            _managerThread.Start();
        }

        /// <summary>
        /// Stops the manager background thread
        /// </summary>
        private void StopThread()
        {
            if (IsAlive)
            {
                _managerThread.Abort();
                _managerThread.Join(60000);
            }

            _managerThread = null;
        }

        /// <summary>
        /// Pauses the next cycle for the background thread
        /// </summary>
        private void PauseThread()
        {
            _threadSuspended.Reset();
        }

        /// <summary>
        /// Resumes the next cycle for the background thread
        /// </summary>
        private void ResumeThread()
        {
            _threadSuspended.Set();
        }

        #endregion

        #region Main Thread

        /// <summary>
        /// Background manager thread for core thread
        /// </summary>
        private void ManagerThread()
        {
            DateTime schedulesRetrieveDate = DateTime.Now.Date;

            // Catch thread aborting
            try
            {

                LoadAllSchedules();

                HandleQueues();

                while (true)
                {
                    // This is used for pausing the process
                    _threadSuspended.WaitOne();

                    if (DateTime.Now.Date > schedulesRetrieveDate)
                    {
                        LoadAllSchedules();
                    }

                    Thread.Sleep(60000);
                }
            }
            catch (ThreadAbortException)
            {

            }
        }

        #endregion

        #region Main Thread Methods

        /// <summary>
        /// Main thread processing method for loading all schedules from the server
        /// </summary>
        private void LoadAllSchedules()
        {
            _scheduleManager.ClearSchedules();

            //Retrieve list from Comm manager
            Dictionary<Guid, string> schedules = _communicationManager.RetrieveSchedules();

            if (schedules == null)
            {
                return;
            }

            foreach (KeyValuePair<Guid, string> schedule in schedules)
            {
                _scheduleManager.LoadSchedule(schedule.Key, schedule.Value);
            }
        }

        /// <summary>
        /// Main thread processing method for checking the process queue for new tasks
        /// </summary>
        private void HandleQueues()
        {
            lock (_processLock)
            {
                if (_queuedProcessingSets.Count > 0) //Add isActive somehow
                {
                    //Start new workerthread for the processing to minimize the delay on the main "timing" thread
                    ThreadPool.QueueUserWorkItem(ProcessScheduleQueueCallback, _queuedProcessingSets.Dequeue());
                }
            }
        }

        #endregion

        #region Private WorkItemThread Callbacks

        /// <summary>
        /// ThreadPool callback method for starting the processing process
        /// </summary>
        /// <param name="state">ProcessingSet with information about the upcoming task</param>
        private void ProcessScheduleQueueCallback(object state)
        {
            ProcessingSet processingSet = (ProcessingSet)state;

            _processingManager.Process(processingSet);
        }

        #endregion

        #region Manager Event Handlers

        /// <summary>
        /// Event handler for the Schedule Manager notifying that a schedule has been triggered
        /// </summary>
        /// <param name="sender">IScheduleManager calling this method</param>
        /// <param name="e">ScheduleFiredEventArgs with information about the event</param>
        private void ScheduleManager_ScheduleFired(object sender, ScheduleFiredEventArgs e)
        {
            ProcessingSet? processingSet = _communicationManager.RetrieveProcessingInstructions(e.ScheduleID);

            if (!processingSet.HasValue)
            {
                return;
            }

            lock (_processLock)
            {
                _queuedProcessingSets.Enqueue(processingSet.Value);
            }
        }

        #endregion
    }
}